/*
 * Copyright (c) 2011. c.mueller
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * The Software shall be used for Good, not Evil.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package de.altitude.cimuelle;

import de.altitude.cimuelle.utils.ServerOperations;
import de.altitudecustomcommands.commandHandler;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.json.simple.parser.ContainerFactory;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import javax.naming.ConfigurationException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;

/**
 * User: cybernaut
 * Date: 11.06.11
 * Time: 23:20
 * $Id: LogParser.java 2 2011-10-22 18:53:00Z breamond $
 */
public class LogParser {
    private final static String version = "1.04";
    private String filename = "";
    private long sleepTime = 1;

    private Config props = null;
    private final String mainConfigFile;

    private String userConfigFile;
    private ContainerFactory containerFactory;
    private final JSONParser parser = new JSONParser();

    private ParseAltitudeLine parseAltitude;
    private static final Logger logger = Logger.getLogger("LogParser");
    private final static Logger logger2 = Logger.getLogger("MyTailerListener");
    // commons IO has a bug which results in reading the whole log file again
    private boolean readLines = true;
    private Thread tailThread;
    private Thread tailWatchThread;
    private static LogParser mainThread;
    @SuppressWarnings({"FieldCanBeLocal"})
    private Thread doAtStop;

    private LogParser(String configFile) {
        this.mainConfigFile = configFile;
        props = Config.getInstance(configFile);

        this.sleepTime = Long.parseLong(props.getConfig("sleepTime", String.valueOf(sleepTime)));
        this.filename = props.getLogFile("filename", "");
        if ("".equals(this.filename)) {
            this.filename = props.getLogFile("logfile", filename);
        }

        printStart();
    }

    private LogParser(String configFile, String userConfigFile) {
        this.mainConfigFile = configFile;
        this.userConfigFile = userConfigFile;

        props = Config.getInstance(configFile, userConfigFile);
        this.sleepTime = Long.parseLong(props.getConfig("sleepTime", String.valueOf(sleepTime)));
        // backward compatibility
        this.filename = props.getLogFile("filename", "");
        if ("".equals(this.filename)) {
            this.filename = props.getLogFile("logfile", filename);
        }
        printStart();
    }

    private void printStart() {
        StringBuilder sb = new StringBuilder();
        sb.append("Starting Altitude log parser:\n");
        sb.append("\tMain config file: '");
        sb.append(mainConfigFile);
        sb.append("'\n");
        if (userConfigFile != null) {
            sb.append("\tUser config file: '");
            sb.append(userConfigFile);
            sb.append("'\n");
        }
        sb.append("\tLog file to parse: '").append(filename).append("'");
        sb.append("\tCommand file to use: '").append(props.getCommandFile()).append("'");
        sb.append("\tDebug Mode: ").append(props.getConfig("debug", "false")).append("\n");
        logger.info(sb.toString());
    }

    synchronized void doParse() throws ConfigurationException {
        final File file = new File(filename);

        parseAltitude = new ParseAltitudeLine();

        containerFactory = new ContainerFactory() {
            public List creatArrayContainer() {
                return new LinkedList();
            }

            public Map createObjectContainer() {
                return new LinkedHashMap();
            }
        };

        createTailThread(file);
        createAndStartTailWatchThread(file);

        /*listener = new MyTailerListener();
        final Tailer tailer = new Tailer(file, listener, sleepTime, true);
        //final Tailer tailer = new Tailer(file, listener, sleepTime);

        Thread thread = new Thread(tailer);
        thread.setDaemon(false); // optional
        thread.start();
        */
        doAtStop = new Thread() {
            @Override
            public void run() {
                logger.info("Shutting down");
                MessageTask.getInstance().stopTasks();
                commandHandler.timerMessage.interrupt();
                try {
                    commandHandler.timerMessage.join(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                readLines = false;
                tailThread.interrupt();
                tailWatchThread.interrupt();
                //tailer.stop();
            }
        };

        Runtime.getRuntime().addShutdownHook(doAtStop);

        initPlayerList();

    }

    private void initPlayerList() {
        ServerOperations.sendServerStatusCommand();
    }

    private void createAndStartTailWatchThread(final File file) {
        tailWatchThread = new Thread() {
            final Logger log = Logger.getLogger("LogParserThread2");

            @Override
            public void run() {
                long lastSize = file.length();
                log.info("Read File '" + file.getAbsolutePath() + "' with size '" + file.length() + "'");
                while (readLines) {
                    if (lastSize > file.length()) {
                        // file rotated
                        log.info("File got rotated. Restarting Thread");
                        lastSize = file.length();
                        restartTailThread(file);
                    } else {
                        if (tailThread != null && !tailThread.isAlive()) {
                            log.error("Tail is not active, starting Thread!");
                            createTailThread(file);
                            tailThread.start();
                        }
                        lastSize = file.length();
                    }
                    try {
                        sleep(((sleepTime <= 0.1) ? (long) 0.1 : sleepTime) * 1000);
                    } catch (InterruptedException e) {
                        log.error("I got interrupted.");
                        // expected
                    }
                }
            }
        };
        tailWatchThread.start();
    }

    private void restartTailThread(File file) {
        tailThread.interrupt();
        while (tailThread.isAlive()) {
            logger.info("Tail is still active.");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        logger.info("Tail is stopped.");
        createTailThread(file);
    }

    private void createTailThread(final File file) {
        tailThread = new Thread() {
            final Logger log = Logger.getLogger("tailThread");

            public final Runtime r = Runtime.getRuntime();
            public Process p;
            public Scanner s;

            @Override
            public void run() {
                try {
                    String command = "tail -f -n 1 " + file.getAbsolutePath();
                    log.info("Thread starts '" + command + "'");
                    p = r.exec(command);
                    s = new Scanner(p.getInputStream());

                    while (readLines && s.hasNextLine()) {
                        String line = s.nextLine();
                        try {
                            Map json = (Map) parser.parse(line, containerFactory);
                            parseAltitude.parseJsonArray(json);
                        } catch (ParseException pe) {
                            log.error(line);
                            log.error(pe);
                        }
                    }
                } catch (IOException e) {
                    log.error(e.getMessage());
                    e.printStackTrace();
                } finally {
                    log.info("Tail Thread stopped.");
                }
            }

            @Override
            public void interrupt() {
                if (p != null) {
                    p.destroy();
                }
                super.interrupt();
            }
        };
    }

    public static void main(String[] args) throws ConfigurationException {
        String log4jName = "log4j.properties";
        File log4j = new File(log4jName);
        if (log4j.exists()) {
            PropertyConfigurator.configureAndWatch(log4j.getAbsolutePath());
        } else {
            Class resourceClass = LogParser.class;
            URL url = resourceClass.getResource("/log4j_internal.properties");
            PropertyConfigurator.configure(url);
        }

        logger.info("Starting...");
        logger.info("Version: " + version);

        if (args.length == 1) {
            mainThread = new LogParser(args[0]);
        } else if (args.length == 2) {
            mainThread = new LogParser(args[0], args[1]);
        } else {
            System.out.println("" +
                    "Usage: java -jar file.jar \n" +
                    "   [path to configfile]\n" +
                    "   [path to userconfigfile]\n" +
                    "\n" +
                    "       configfile:\n" +
                    "           port.active=(true|false) : active for server with port x as number" +
                    "           [port.]welcomeMessage : send entry message\n" +
                    "           commandfile : command.txt from server\n" +
                    "           [filename] : log.txt file from server\n" +
                    "           [updatetime] : sec between updates\n" +
                    "           [sleepTime] : sleep between updates\n" +
                    "           [configReloadTime] : sleep between config update check\n" +
                    "           [debug=(true|false)] : Debug output" +
                    "");
        }
        if (mainThread != null) {
            mainThread.doParse();
        }
    }

    public static void externalStop() {
        logger.info("External stop called.");
        System.exit(0);
    }

}